// check pointer
owl_bool owl_check_arr_pointer(owl_byte * src, owl_umax size)
{
  return (src != NULL && size >= sizeof(owl_arr));
}

// check members
owl_bool owl_check_arr_members(owl_byte * ptr, owl_umax arr_size, owl_type type, owl_umax var_size,
                               owl_umax (* print_elem)(owl_byte *, owl_umax size),
                               owl_char (* comp_elem)(owl_byte *, owl_byte *))
{
  return (ptr != NULL && arr_size != 0 && type != OWL_TYPE_NONE
          && var_size != 0 && print_elem != NULL && comp_elem != NULL);
}

// check all
owl_bool owl_check_arr_all(owl_arr * data, owl_umax size)
{
  return owl_check_arr_pointer((void *) data, size) 
         && owl_check_arr_members(data -> arr -> ptr, data -> arr -> size,
                                  data -> var.type, data -> var.size, 
                                  data -> var.print_elem, data -> var.comp_elem);
}

// create
owl_arr * owl_create_arr(owl_byte * data, owl_umax arr_size, owl_type type, owl_umax var_size,
                         owl_umax (* print_elem)(owl_byte *, owl_umax size),
                         owl_char (* comp_elem)(owl_byte *, owl_byte *))
{
  // check params
  owl_arr * arr = NULL;
  owl_mem_space * mem_space = NULL;
  if (owl_check_arr_members(data, arr_size, type, var_size, print_elem, comp_elem) == owl_false)
    goto err;
  // create the structure 
  // it will need 2 allocations:
  // - one for the structure itself
  // - the other one for the mem_space inside the struct
  arr = (void *) (owl_create_mem_space(sizeof(owl_arr)) -> ptr);
  if (arr == NULL)
    goto err;
  mem_space = (owl_mem_space *) owl_create_bytarr(data, arr_size * var_size);
  if (mem_space == NULL)
    goto err;
  // assign the rest of the variables
  arr -> arr = mem_space;
  arr -> var.type = type;
  arr -> var.size = var_size;
  arr -> var.print_elem = print_elem;
  arr -> var.comp_elem = comp_elem;
  return arr; // first time returning such a weird struct
  
  // error
  err:
  ;
  owl_free_mem_space(&((owl_mem_space) {(void *) arr, sizeof(owl_arr)}));
  owl_free_mem_space(mem_space);
  return NULL;
}

// free
owl_umax owl_free_arr(owl_arr * arr)
{
  // check params
  if (owl_check_arr_all((void *) arr, sizeof(owl_arr)) == owl_false)
    goto err;
  // free the memory allocated by the struct
  owl_umax tmp = owl_free_mem_space(arr -> arr);
  if (tmp == 0)
    goto err;
  owl_mem_space sp = {(void *) arr, sizeof(owl_arr)};
  tmp = owl_free_mem_space(&sp);
  if (tmp == 0)
    goto err;  
  // return the number of free slots on the mem_space table
  return tmp;
  err:
  return 0;
}

// print
owl_umax owl_print_arr(owl_byte * src, owl_umax size)
{
  // check params
  printf("OWL_ARR struct info:\n");
  printf("  Struct info: %p, %llu\n", src, size);
  if (owl_check_arr_pointer(src, size) == owl_false)
    return 0;
  
  // else print the structure
  owl_arr * arr = (void *) src;
  printf("  Variable type: %u\n", arr -> var.type);
  printf("  Variable size (bytes): %llu\n", arr -> var.size);
  printf("  Print function: %p\n", arr -> var.print_elem);
  printf("  Compare function: %p\n", arr -> var.comp_elem);
  printf("  Array pointer: %p\n", arr -> arr -> ptr);
  printf("  Array size (bytes): %llu\n", arr -> arr -> size);
  printf("  Array contents: ");
  for (owl_umax i = 0; i < (arr -> arr -> size); i += arr -> var.size) {
    arr -> var.print_elem((arr -> arr -> ptr) + i, (arr -> arr -> size) - i);
    putchar(' ');
  }
  putchar('\n');
  return sizeof(owl_arr);
}

// compare
owl_char owl_comp_arr(owl_byte * arr1, owl_byte * arr2)
{
  // check params
  owl_arr * arr1_tmp = (void *) arr1,
          * arr2_tmp = (void *) arr2;
  // check params
  if (owl_check_arr_all(arr1_tmp, sizeof(owl_arr)) == owl_false
      || owl_check_arr_all(arr2_tmp, sizeof(owl_arr)) == owl_false)
      return owl_comp_err;
  // do the comparison, size and element contents matter
  // arr size -> var size -> var type -> element content
  // arr size
  if (arr1_tmp -> arr -> size < arr2_tmp -> arr -> size)
    return owl_comp_less;
  else if (arr1_tmp -> arr -> size > arr2_tmp -> arr -> size)
    return owl_comp_greater;
  // var size
  if (arr1_tmp -> var.size < arr2_tmp -> var.size)
    return owl_comp_less;
  else if (arr1_tmp -> var.size > arr2_tmp -> var.size)
    return owl_comp_greater;
  // var type
  if (arr1_tmp -> var.type < arr2_tmp -> var.type)
    return owl_comp_less;
  else if (arr1_tmp -> var.type > arr2_tmp -> var.type)
    return owl_comp_greater;
  // element comparison if types are equal
  // first element comparison to trigger the < or > expressions
  for (owl_umax i = 0; i < arr1_tmp -> arr -> size; i += arr1_tmp -> var.size) {
    owl_bool tmp_bool = arr1_tmp -> var.comp_elem(arr1_tmp -> arr -> ptr + i, arr2_tmp -> arr -> ptr + i);
    if (tmp_bool == owl_comp_less || tmp_bool == owl_comp_greater)
      return tmp_bool;
  }
  // legit real equal arrays
  return owl_comp_equal;
}

// reverse the contents of an array
owl_umax owl_reverse_arr(owl_arr * arr)
{
  // check params
  if (owl_check_arr_all(arr, sizeof(owl_arr)) == owl_false)
    return 0;
  // else do the reversing process
  owl_bytarr * tmp = owl_create_bytarr(NULL, arr -> var.size);
  owl_bytarr tmp1 = {arr -> arr -> ptr, arr -> var.size},
             tmp2 = {arr -> arr -> ptr + arr -> arr -> size - (arr -> var.size), arr -> var.size};
  if (tmp -> ptr == NULL)
    return 0;
  owl_umax middle_pos = (arr -> arr -> size / arr -> var.size) / 2;
  for (owl_umax i = 0; i < middle_pos; i++) {
    owl_copy_bytarr(&tmp1, tmp);
    owl_copy_bytarr(&tmp2, &tmp1);
    owl_copy_bytarr(tmp, &tmp2);
    tmp1.ptr += arr -> var.size;
    tmp2.ptr -= arr -> var.size;
  }
  // return the number of bytes moved
  owl_free_bytarr(tmp);
  return arr -> arr -> size;
}

// find an element inside an array
owl_byte * owl_find_arr_elem(owl_arr * arr, owl_byte * elem, owl_umax size)
{
  // check params
  if (owl_check_arr_all(arr, sizeof(owl_arr)) == owl_false
      || elem == NULL || size == 0 || size < arr -> var.size)
    return NULL;
  // find the element
  for (owl_umax i = 0; i < arr -> arr -> size; i += arr -> var.size)
    if ((arr -> var.comp_elem(arr -> arr -> ptr + i, elem)) == owl_comp_equal)
      return arr -> arr -> ptr + i;
  // not found
  return NULL;
}
